From NHS Service Search , surgeries within 50 miles serving Hoe Street, Wolthamstow. Postcodes provided and will need to be geocoded. Investigate whether coordinates are available in NHS developer portal, registration required.
def get_surgery_locs_nominatim( df: pd.DataFrame, out_pth: Path, sleep_s:float=2.0, user: str= USER_AGENT ) ->None:"""Use orgnm and then postcode to attempt geolocating surgeries. Uses Nominatim. Writes to file if error encountered. """ tmp_df = df.copy(deep=True)# Instantiate a new Nominatim client app = Nominatim( user_agent="") tmp_df["lat"] =0.0 tmp_df["lon"] =0.0 tmp_df["geocode_type"] ="None" geocode_type = []for i, row in tmp_df.iterrows(): loc = {"lon": 0.0, "lat": 0.0} geo_type ="None"try: loc = app.geocode( query=row["Organisation Name"], country_codes="gb", viewbox=[ point.Point(51.375, -0.509), point.Point(51.868, 0.530)], bounded=True)print("Geocode by Org Name success") sleep(sleep_s) geo_type ="from_nm"ifnot loc:# case where name did not return a location loc = app.geocode( query=row["PostCode"], country_codes="gb", viewbox=[ point.Point(51.375, -0.509), point.Point(51.868, 0.530) ], bounded=True)print("Geocode by Postcode success") sleep(sleep_s) geo_type ="from_pcd"except GeocoderUnavailable:# nominatim is downprint(f"Breaking on {row['Organisation Name']}")breakfinally:# update the geometry columnif loc: loc = loc.raw tmp_df.loc[i, ["lat"]] = loc["lat"] tmp_df.loc[i, ["lon"]] = loc["lon"] tmp_df.loc[i, ["geocode_type"]] = geo_typeelse:break tmp_df.to_csv(out_pth)return tmp_df
Click to show code
# execute once only as hammers the nominatim servicegeocoded_surgeries_pth = here("data/external/features/geocoded-london-surgeries.csv")ifnot os.path.exists(geocoded_surgeries_pth): get_surgery_locs_nominatim(df=surgeries, out_pth=cache_pth, sleep_s=2.0)
Make this Notebook Trusted to load map: File -> Trust Notebook
Note that the accuracy of surgery location varies dependent upon whether the surgeries were geolocated from the organisation name or the postcode. Most are from postcode. Accuracy of these points will depend on size of postcode.
Click to show code
code_stats = geocd_surgeries["geocode_type"].describe()print(f"{round((code_stats.freq /len(geocd_surgeries)) *100, 1)} % of "f"{len(geocd_surgeries)} surgeries were geocoded by postcode.")
57.4 % of 535 surgeries were geocoded by postcode.
Ingest Pop-weighted centroids.
Click to show code
ENDPOINT ="https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/Output_Areas_2021_PWC_V3/FeatureServer/0/query"PARAMS = {"where": "OA21CD like 'E%'","f": "geoJSON", "outFields": "*","outSR": 4326,"returnCountOnly": True,}resp = requests.get(ENDPOINT, PARAMS)cont = resp.json()n_centroids = cont['properties']['count']print(f"There are {n_centroids:,} OA Centroids with the specified OA21CD pattern" )
There are 178,605 OA Centroids with the specified OA21CD pattern
Going with LAD21 to match centroid release, though 23 is available. LADCD for Wolthamstow is E09000031.
Click to show code
ENDPOINT ="https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/LAD_Dec_2021_GB_BFC_2022/FeatureServer/0/query"PARAMS["where"] ="LAD21CD = 'E09000031'"del PARAMS["resultOffset"]PARAMS["outSR"] =27700# get in BNG as will need to buffer_, boundary = get_ons_geo_data(ENDPOINT, PARAMS)boundary.explore()
Make this Notebook Trusted to load map: File -> Trust Notebook
Buffer the LAD boundary to avoid edge effects - people may prefer surgeries in the adjacent local authority. Buffer by 5km, pretty arbitrary rule of thumb that can be adjusted to suit.
Make this Notebook Trusted to load map: File -> Trust Notebook
This is an interesting situation - the high density of destinations in this locality could form the basis for a different treatment of proximal surgeries. The median travel time will be from one output area centroid to every GP on the map.
It may not be reasonable to consider all these options for each centroid. You could potentially draw a smaller buffer around each output area instead, and then calculate travel times to a smaller subset of proximal GPs.